'use client';
import * as React from 'react';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { useStoreEffect } from '@mui/x-internals/store';
import { useAssertModelConsistency } from '@mui/x-internals/useAssertModelConsistency';
import { warnOnce } from '@mui/x-internals/warning';
import { type PointerGestureEventData } from '@mui/x-internal-gestures/core';
import { type ChartPlugin } from '../../models';
import { type UseChartCartesianAxisSignature } from './useChartCartesianAxis.types';
import { rainbowSurgePalette } from '../../../../colorPalettes';
import { useSelector } from '../../../store/useSelector';
import { selectorChartDrawingArea } from '../../corePlugins/useChartDimensions/useChartDimensions.selectors';
import { selectorChartSeriesProcessed } from '../../corePlugins/useChartSeries/useChartSeries.selectors';
import { defaultizeXAxis, defaultizeYAxis } from './defaultizeAxis';
import { selectorChartXAxis, selectorChartYAxis } from './useChartCartesianAxisRendering.selectors';
import { getAxisIndex } from './getAxisValue';
import { getSVGPoint } from '../../../getSVGPoint';
import { selectorChartsInteractionIsInitialized } from '../useChartInteraction';
import { selectorChartAxisInteraction } from './useChartCartesianInteraction.selectors';
import { checkHasInteractionPlugin } from '../useChartInteraction/checkHasInteractionPlugin';

export const useChartCartesianAxis: ChartPlugin<UseChartCartesianAxisSignature<any>> = ({
  params,
  store,
  seriesConfig,
  svgRef,
  instance,
}) => {
  const { xAxis, yAxis, dataset, onHighlightedAxisChange } = params;

  if (process.env.NODE_ENV !== 'production') {
    const ids = [...(xAxis ?? []), ...(yAxis ?? [])]
      .filter((axis) => axis.id)
      .map((axis) => axis.id);
    const duplicates = new Set(ids.filter((id, index) => ids.indexOf(id) !== index));
    if (duplicates.size > 0) {
      warnOnce(
        [
          `MUI X Charts: The following axis ids are duplicated: ${Array.from(duplicates).join(', ')}.`,
          `Please make sure that each axis has a unique id.`,
        ].join('\n'),
        'error',
      );
    }
  }

  const drawingArea = useSelector(store, selectorChartDrawingArea);
  const processedSeries = useSelector(store, selectorChartSeriesProcessed);

  const isInteractionEnabled = useSelector(store, selectorChartsInteractionIsInitialized);
  const { axis: xAxisWithScale, axisIds: xAxisIds } = useSelector(store, selectorChartXAxis);
  const { axis: yAxisWithScale, axisIds: yAxisIds } = useSelector(store, selectorChartYAxis);

  useAssertModelConsistency({
    warningPrefix: 'MUI X Charts',
    componentName: 'Chart',
    propName: 'highlightedAxis',
    controlled: params.highlightedAxis,
    defaultValue: undefined,
  });

  useEnhancedEffect(() => {
    if (params.highlightedAxis !== undefined) {
      store.set('controlledCartesianAxisHighlight', params.highlightedAxis);
    }
  }, [store, params.highlightedAxis]);

  // The effect do not track any value defined synchronously during the 1st render by hooks called after `useChartCartesianAxis`
  // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
  const isFirstRender = React.useRef(true);
  React.useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }

    store.set('cartesianAxis', {
      x: defaultizeXAxis(xAxis, dataset),
      y: defaultizeYAxis(yAxis, dataset),
    });
  }, [seriesConfig, drawingArea, xAxis, yAxis, dataset, store]);

  const usedXAxis = xAxisIds[0];
  const usedYAxis = yAxisIds[0];

  useStoreEffect(
    store,
    selectorChartAxisInteraction,
    (prevAxisInteraction, nextAxisInteraction) => {
      if (!onHighlightedAxisChange) {
        return;
      }
      if (Object.is(prevAxisInteraction, nextAxisInteraction)) {
        return;
      }

      if (prevAxisInteraction.length !== nextAxisInteraction.length) {
        onHighlightedAxisChange!(nextAxisInteraction);
        return;
      }
      if (
        prevAxisInteraction?.some(
          ({ axisId, dataIndex }, itemIndex) =>
            nextAxisInteraction[itemIndex].axisId !== axisId ||
            nextAxisInteraction[itemIndex].dataIndex !== dataIndex,
        )
      ) {
        onHighlightedAxisChange!(nextAxisInteraction);
      }
    },
  );

  const hasInteractionPlugin = checkHasInteractionPlugin(instance);

  React.useEffect(() => {
    const element = svgRef.current;
    if (!isInteractionEnabled || !hasInteractionPlugin || !element || params.disableAxisListener) {
      return () => {};
    }

    // Clean the interaction when the mouse leaves the chart.
    const moveEndHandler = instance.addInteractionListener('moveEnd', (event) => {
      if (!event.detail.activeGestures.pan) {
        instance.cleanInteraction();
      }
    });
    const panEndHandler = instance.addInteractionListener('panEnd', (event) => {
      if (!event.detail.activeGestures.move) {
        instance.cleanInteraction();
      }
    });
    const pressEndHandler = instance.addInteractionListener('quickPressEnd', (event) => {
      if (!event.detail.activeGestures.move && !event.detail.activeGestures.pan) {
        instance.cleanInteraction();
      }
    });

    const gestureHandler = (event: CustomEvent<PointerGestureEventData>) => {
      const srvEvent = event.detail.srcEvent;
      const target = event.detail.target as SVGElement | undefined;
      const svgPoint = getSVGPoint(element, srvEvent);

      // Release the pointer capture if we are panning, as this would cause the tooltip to
      // be locked to the first "section" it touches.
      if (
        event.detail.srcEvent.buttons >= 1 &&
        target?.hasPointerCapture(event.detail.srcEvent.pointerId) &&
        !target?.closest('[data-charts-zoom-slider]')
      ) {
        target?.releasePointerCapture(event.detail.srcEvent.pointerId);
      }
      if (!instance.isPointInside(svgPoint.x, svgPoint.y, target)) {
        instance.cleanInteraction?.();

        return;
      }
      instance.setPointerCoordinate(svgPoint);
    };

    const moveHandler = instance.addInteractionListener('move', gestureHandler);
    const panHandler = instance.addInteractionListener('pan', gestureHandler);
    const pressHandler = instance.addInteractionListener('quickPress', gestureHandler);

    return () => {
      moveHandler.cleanup();
      moveEndHandler.cleanup();
      panHandler.cleanup();
      panEndHandler.cleanup();
      pressHandler.cleanup();
      pressEndHandler.cleanup();
    };
  }, [
    svgRef,
    store,
    xAxisWithScale,
    usedXAxis,
    yAxisWithScale,
    usedYAxis,
    instance,
    params.disableAxisListener,
    isInteractionEnabled,
    hasInteractionPlugin,
  ]);

  React.useEffect(() => {
    const element = svgRef.current;
    const onAxisClick = params.onAxisClick;
    if (element === null || !onAxisClick) {
      return () => {};
    }

    const axisClickHandler = instance.addInteractionListener('tap', (event) => {
      let dataIndex: number | null = null;
      let isXAxis: boolean = false;

      const svgPoint = getSVGPoint(element, event.detail.srcEvent);

      const xIndex = getAxisIndex(xAxisWithScale[usedXAxis], svgPoint.x);
      isXAxis = xIndex !== -1;

      dataIndex = isXAxis ? xIndex : getAxisIndex(yAxisWithScale[usedYAxis], svgPoint.y);

      const USED_AXIS_ID = isXAxis ? xAxisIds[0] : yAxisIds[0];
      if (dataIndex == null || dataIndex === -1) {
        return;
      }

      // The .data exist because otherwise the dataIndex would be null or -1.
      const axisValue = (isXAxis ? xAxisWithScale : yAxisWithScale)[USED_AXIS_ID].data![dataIndex];

      const seriesValues: Record<string, number | null | undefined> = {};

      Object.keys(processedSeries)
        .filter((seriesType): seriesType is 'bar' | 'line' => ['bar', 'line'].includes(seriesType))
        .forEach((seriesType) => {
          processedSeries[seriesType]?.seriesOrder.forEach((seriesId) => {
            const seriesItem = processedSeries[seriesType]!.series[seriesId];

            const providedXAxisId = seriesItem.xAxisId;
            const providedYAxisId = seriesItem.yAxisId;

            const axisKey = isXAxis ? providedXAxisId : providedYAxisId;
            if (axisKey === undefined || axisKey === USED_AXIS_ID) {
              seriesValues[seriesId] = seriesItem.data[dataIndex];
            }
          });
        });

      onAxisClick(event.detail.srcEvent, { dataIndex, axisValue, seriesValues });
    });

    return () => {
      axisClickHandler.cleanup();
    };
  }, [
    params.onAxisClick,
    processedSeries,
    svgRef,
    xAxisWithScale,
    xAxisIds,
    yAxisWithScale,
    yAxisIds,
    usedXAxis,
    usedYAxis,
    instance,
  ]);

  return {};
};

useChartCartesianAxis.params = {
  xAxis: true,
  yAxis: true,
  dataset: true,
  onAxisClick: true,
  disableAxisListener: true,
  onHighlightedAxisChange: true,
  highlightedAxis: true,
};

useChartCartesianAxis.getDefaultizedParams = ({ params }) => {
  return {
    ...params,
    colors: params.colors ?? rainbowSurgePalette,
    theme: params.theme ?? 'light',
    defaultizedXAxis: defaultizeXAxis(params.xAxis, params.dataset),
    defaultizedYAxis: defaultizeYAxis(params.yAxis, params.dataset),
  };
};

useChartCartesianAxis.getInitialState = (params) => ({
  cartesianAxis: {
    x: params.defaultizedXAxis,
    y: params.defaultizedYAxis,
  },
  ...(params.highlightedAxis === undefined
    ? {}
    : {
        controlledCartesianAxisHighlight: params.highlightedAxis,
      }),
});
